package mgoasync

import (
	"container/list"
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
)

// 任务模式
type Mode int

const (
	ModeInsert Mode = 0 // 插入任务，待插入数据在生成后不会再发生改变，按照生成顺序队列插入数据库后即可释放
	ModeUpdate Mode = 1 // 更新任务，数据实体在内存缓存中会频繁发生改动，定期将数据实体重新更新回数据库
)

const (
	StatusIdle     = int32(0)
	StatusRunning  = int32(1)
	StatusQuitting = int32(2)
)

// 后台任务
type Task struct {
	sync.RWMutex
	id           int               // 后台任务 ID
	mode         Mode              // 后台任务模式
	mgoColl      *mongo.Collection // MongoDB 数据集合
	interval     time.Duration     // 间隔时间
	ticker       *time.Ticker      // 定时器
	closing      chan bool         // 退出中信号通道
	closed       chan bool         // 退出完成信号通道
	status       int32             // 任务状态
	queue        *list.List        // 待写入的数据队列
	pendingDatas interface{}       // 如果发生写入失败，则将数据缓存在此，并在3秒钟后尝试重新写入
}

// 构造一个后台任务，传入对应的 MongoDB 数据集合
func newTask(coll *mongo.Collection) *Task {
	task := &Task{
		mode:     ModeUpdate,
		mgoColl:  coll,
		interval: time.Minute,
		status:   StatusIdle,
		queue:    list.New(),
	}
	cache.AddTask(task)
	return task
}
func (task *Task) isIdle() bool           { return atomic.LoadInt32(&task.status) == StatusIdle }
func (task *Task) isQuiting() bool        { return atomic.LoadInt32(&task.status) == StatusQuitting }
func (task *Task) setStatus(status int32) { atomic.StoreInt32(&task.status, status) }

// 设置后台任务的时间间隔
func (task *Task) SetInterval(interval time.Duration) {
	task.Lock()
	defer task.Unlock()
	task.interval = interval
	if task.ticker != nil {
		task.ticker.Reset(interval)
	}
}

// 开始任务
func (task *Task) begin() error {
	task.Lock()
	defer task.Unlock()
	if !task.isIdle() {
		return fmt.Errorf(`任务已经在运行状态`)
	}
	task.closing = make(chan bool)
	task.closed = make(chan bool)
	task.ticker = time.NewTicker(task.interval)
	task.setStatus(StatusRunning)
	return nil
}

// 关闭任务
func (task *Task) Close() chan bool {
	task.Lock()
	defer task.Unlock()
	task.setStatus(StatusQuitting)
	task.ticker.Stop()
	close(task.closing)
	return task.closed
}

// 后台任务缓存池
type TaskCache struct {
	sync.RWMutex
	pool      map[int]*Task
	idCounter int
}

var cache = &TaskCache{
	pool: map[int]*Task{},
}

// 添加任务进缓存池
func (c *TaskCache) AddTask(task *Task) {
	c.Lock()
	defer c.Unlock()
	task.id = c.idCounter
	c.idCounter++
	c.pool[task.id] = task
}
